const http = require("http");
const context = require("./context");
const request = require("./request");
const response = require("./response");
const { Stream } = require("stream");

class Application {
  constructor() {
    this.middleware = []; // 保存用户添加的中间件函数

    // 防止多个对象Application共享同一个context、request、response对象
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }

  use(fn) {
    this.middleware.push(fn);
  }

  callback() {
    const fnMiddleware = this.compose(this.middleware);

    const handleRequest = (req, res) => {
      // 每个请求都会创建一个独立的 Context 上下文对象，他们之间不会互相污染
      const context = this.createContext(req, res);
      fnMiddleware(context)
        .then(() => {
          respond(context);
          // res.end(context.body);
        })
        .catch((error) => {
          res.end(error.message);
        });
    };

    return handleRequest;
  }

  // 异步递归遍历调用中间件处理函数
  compose(middleware) {
    return function (context) {
      const dispatch = (index) => {
        if (index === middleware.length) return Promise.resolve();
        const fn = middleware[index];
        return Promise.resolve(fn(context, () => dispatch(index + 1)));
      };

      // 返回第一个中间件处理函数
      return dispatch(0);
    };
  }

  // 构造上下文对象
  createContext(req, res) {
    const context = Object.create(this.context);
    const request = (context.request = Object.create(this.request));
    const response = (context.response = Object.create(this.response));

    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    context.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.state = {};
    return context;
  }
}

function respond(ctx) {
  const body = ctx.body;
  const res = ctx.res;

  if (typeof body === "string") return res.end(body);
  if (Buffer.isBuffer(body)) return res.end(body);
  if (body instanceof Stream) return body.pipe(ctx.res);
  if (typeof body === "number") return res.end(body.toString());
  if (typeof body === "object" && body !== null) {
    const jsonStr = JSON.stringify(body);
    return res.end(jsonStr);
  }

  res.statusCode = 204;
  res.end();
}

module.exports = Application;
